package com.sky.aopaspect;

import com.sky.anno.AutoFill;
import com.sky.constant.AutoFillConstant;
import com.sky.context.ThreadLocalContext;
import com.sky.enumclass.OperationType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.time.LocalDateTime;

@Aspect
@Component//加入spring的IOC容器中,以便底层生成目标对象的代理对象
@Slf4j
public class AutoFillAspect {

    //定义切点
/*    @Pointcut("@annotation(com.sky.anno.AutoFill) && execution(* com.sky.mapper..*.*(..))")
    public void pointcut() {
    }*/


    //定义切点
    @Pointcut("@annotation(com.sky.anno.AutoFill) && execution(* com.sky.mapper..*.*(..))")
    private void autoFillPointcut() {

    }

    //定义通知
    @Around("autoFillPointcut()")
    public Object autoFill(ProceedingJoinPoint joinPoint) throws Throwable {
        //前置部分
        log.info("开始对目标方法的入参实体的公共属性进行自动填充");//create_time, update_time, create_user, update_user
        //1.获取目标方法的方法签名,向下转型为MethodSignature
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //2.获取目标方法上的注解.参数为:注解的类型,并且获取注解的属性值:operationType-->数据库操作类型
        AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);
        OperationType operationType = autoFill.operationType();
        //3.获取目标方法的入参-->实体对象
        Object[] joinPointArgs = joinPoint.getArgs();
        if (joinPointArgs == null || joinPointArgs.length == 0) {
            return joinPoint.proceed();//如果入参为空，则不进行自动填充
        }
        //4.我们约定第一个入参是实体对象-->获取入参实体类
        Object entity = joinPointArgs[0];
        //5.准备要填充的字段属性值
        LocalDateTime now = LocalDateTime.now();//获取当前时间 填充的公共属性为:create_time, update_time
        Long currentLocalUserId = ThreadLocalContext.getCurrentLocalUserId();//获取当前登入用户的id 填充的公共属性为:create_user, update_user
        //6.根据不同的操作类型，填充不同的属性值
        if (operationType == OperationType.INSERT) {
            try {
                /*如果是插入操作，则填充create_time, update_time, create_user, update_user*/
                //获取目标方法传入实体类的set方法,根据方法名和入参类型获取方法
                Method setCreateTimeMethod = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);//获取setCreateTime方法
                Method setUpdateTimeMethod = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);//获取setUpdateTime方法
                Method setCreateUserMethod = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);//获取setCreateUser方法
                Method setUpdateUserMethod = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);//获取setUpdateUser方法

                //触发set方法设置属性值,根据实体类对象和属性值设置属性值
                setCreateTimeMethod.invoke(entity, now);//调用传入的实体类对象的setCreateTime方法，设置createTime属性值为当前时间
                setUpdateTimeMethod.invoke(entity, now);//调用传入的实体类对象的setUpdateTime方法，设置updateTime属性值为当前时间
                setCreateUserMethod.invoke(entity, currentLocalUserId);//调用传入的实体类对象的setCreateUser方法，设置createUser属性值为当前登入用户的id
                setUpdateUserMethod.invoke(entity, currentLocalUserId);//调用传入的实体类对象的setUpdateUser方法，设置updateUser属性值为当前登入用户的id
            } catch (Exception e) {
                throw new RuntimeException(e);
            }

        } else if (operationType == OperationType.UPDATE) {
            try {
                /*如果是更新操作，则填充update_time, update_user*/
                //获取目标方法传入实体类的set方法,根据方法名和入参类型获取方法

                Method setUpdateTimeMethod = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);//获取setUpdateTime方法
                Method setUpdateUserMethod = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);//获取setUpdateUser方法

                //触发set方法设置属性值,根据实体类对象和属性值设置属性值
                setUpdateTimeMethod.invoke(entity, now);//调用传入的实体类对象的setUpdateTime方法，设置updateTime属性值为当前时间
                setUpdateUserMethod.invoke(entity, currentLocalUserId);//调用传入的实体类对象的setUpdateUser方法，设置updateUser属性值为当前登入用户的id
            } catch (Exception e) {
                throw new RuntimeException(e);
            }

        }
        //执行目标对象的原有方法
        Object result = joinPoint.proceed();

        //后置部分

        return result;//返回目标对象的原有方法的返回值
    }

    //前置通知
    @Before("autoFillPointcut()")
    public void autoFillBefore(JoinPoint joinPoint) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        //目标对象的原始方法执行前执行的代码
        log.info("开始对目标方法的公共属性进行自动填充");//create_time, update_time, create_user, update_user

        //1. 获取目标方法的反射方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();//获取反射的方法签名
        //2.获取目标方法上的注解
        AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获取方法上的@AutoFill注解
        //3.获取注解的属性值
        OperationType operationType = autoFill.operationType();
        //4.获取目标方法的入参
        Object[] args = joinPoint.getArgs();
        //如果入参为空，则不进行自动填充
        if (args == null || args.length == 0) {
            return;//结束方法
        }
        Object entity = args[0];//获取第一个入参,即要操作的实体对象
        //5.准备要填充的属性值
        //当前时间和当前登入用户的id
        LocalDateTime now = LocalDateTime.now();//获取当前时间
        Long currentLocalUserId = ThreadLocalContext.getCurrentLocalUserId();//获取当前登入用户的id
        //6.根据不同的操作类型，填充不同的属性值
        if (operationType == OperationType.INSERT) {
            //如果是插入操作，则填充create_time, update_time, create_user, update_user
            try {
                //反射设置传入的实体对象的属性值
                /*获取的实体类中声明的方法,根据方法名和入参类型获取方法*/
                Method setCreateTimeMethod = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);//获取setCreateTime方法
                Method setUpdateTimeMethod = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);//获取setUpdateTime方法
                Method setCreateUserMethod = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);//获取setCreateUser方法
                Method setUpdateUserMethod = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);//获取setUpdateUser方法
                //调用方法设置属性值,设置entity对象的属性值
                setCreateTimeMethod.invoke(entity, now);
                setUpdateTimeMethod.invoke(entity, now);
                setCreateUserMethod.invoke(entity, currentLocalUserId);
                setUpdateUserMethod.invoke(entity, currentLocalUserId);
            } catch (Exception e) {
                e.printStackTrace();//打印异常堆栈信息
            }


        } else if (operationType == OperationType.UPDATE) {
            //如果是更新操作，则填充update_time, update_user
            //如果是插入操作，则填充create_time, update_time, create_user, update_user
            try {
                //反射设置传入的实体对象的属性值
                /*获取的实体类中声明的方法,根据方法名和入参类型获取方法*/
                Method setUpdateTimeMethod = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);//获取setUpdateTime方法
                Method setUpdateUserMethod = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);//获取setUpdateUser方法
                //调用方法设置属性值,设置entity对象的属性值
                setUpdateTimeMethod.invoke(entity, now);//调用传入的实体类对象的setUpdateTime方法，设置updateTime属性值为当前时间
                setUpdateUserMethod.invoke(entity, currentLocalUserId);//调用传入的实体类对象的setUpdateUser方法，设置updateUser属性值为当前登入用户的id
            } catch (Exception e) {
                e.printStackTrace();//打印异常堆栈信息
            }

        }


    }



}
